package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import java.util.Map;
import java.util.stream.Collectors;

import static com.tianji.promotion.constants.PromotionConstants.COUPON_CODE_SERIAL_KEY;
import static com.tianji.promotion.constants.PromotionErrConstants.COUPON_NOT_EXISTS;
import static com.tianji.promotion.constants.PromotionErrConstants.COUPON_NOT_EXISTS_OR_USEING;
import static com.tianji.promotion.enums.CouponStatus.*;


/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author huanghui
 * @since 2023-08-02
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private final static String COUPON_CACHE_KEY_PREFIX = "promotion:cache:coupon:";
    private final ICouponScopeService scopeService;
    private final IExchangeCodeService codeService;
    private final StringRedisTemplate  redisTemplate;
    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        //1.保存优惠券
        //1.1.对现有值进行属性拷贝
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        //1.2.保存
        save(coupon);
        //2.范围的判断
        //2.1.判断是否有范围限定，没有范围，直接返回
        if (!dto.getSpecific()){
            return;
        }
        //2.2.保存范围
        Long couponId = coupon.getId();
        List<Long> scopes = dto.getScopes();

        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        log.info("保存到数据库");
        //2.1转变为PO实体，然后存入数据库
        List<CouponScope> list=scopes.stream()
                .map(bizId->new CouponScope().setBizId(bizId)
                        .setCouponId(couponId))
                .collect(Collectors.toList());
        scopeService.saveBatch(list);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        //1.分页查询
        String CouponName = query.getName();
        Integer CouponType = query.getType();
        Integer CouponStatus = query.getStatus();
        Page<Coupon> page = lambdaQuery()
                .eq(CouponType != null, Coupon::getDiscountType, CouponType)
                .eq(CouponStatus != null, Coupon::getStatus, CouponStatus)
                .eq(StringUtils.isNotEmpty(CouponName), Coupon::getName, CouponName)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        //2.处理VO
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);
        log.info("返回优惠券视图实体");
        return PageDTO.of(page,list);
    }

    @Override
    @Transactional
    public void beginIssue(CouponIssueFormDTO dto) {
        log.info("优惠券发放");
        //1.查询优惠券
        Coupon coupon = getById(dto.getId());
        if (coupon == null) {
            throw  new BadRequestException("优惠券不存在");
        }
        // 2.判断优惠券状态，是否是暂停或待发放
        if(coupon.getStatus() != DRAFT&& coupon.getStatus()!=CouponStatus.PAUSE ){
            throw  new BizIllegalException("优惠券发放状态错误");
        }
        // 3.判断是否是立刻发放
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
        //4.更新优惠券
        //4.1.属性拷贝
        Coupon c = BeanUtils.copyBean(dto, Coupon.class);
        //4.2.更新优惠券发放状态
        if (isBegin){
            c.setStatus(CouponStatus.ISSUING);
            c.setIssueBeginTime(now);
        }else {
            c.setStatus(CouponStatus.UN_ISSUE);
        }
        //4.3.写入数据库
        updateById(c);
        //5.兑换码的生成
        /*
        * ①首先判断是否需要生成兑换码
        * ②优惠券类型必须是兑换码
        * ③优惠价状态必须是待发放
        */
        log.info("开始发放兑换码");
        if(coupon.getObtainWay()== ObtainType.ISSUE &&
        coupon.getStatus()== DRAFT){
            coupon.setIssueEndTime(c.getIssueEndTime());
            codeService.asyncGenerateCode(coupon);
        }


    }

    @Override
    @Transactional
    public void updateCoupon(CouponFormDTO couponDTO) {
        //1.校验优惠券兑换码数量
        checkObtainWay(couponDTO);
        //2.查询旧数据
        Coupon oldCoupon = getById(couponDTO.getId());
        if (oldCoupon == null) {
            throw new BadRequestException(COUPON_NOT_EXISTS);
        }

        //3.更新优惠券->属性拷贝
        Coupon coupon = BeanUtils.copyBean(couponDTO, Coupon.class);
        //3.1避免修改状态
        coupon.setStatus(null);
        //3.2 更新
        updateById(coupon);

    }

    @Override
    public CouponDetailVO queryCouponById(Long id) {
        //1.查询优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException(COUPON_NOT_EXISTS);
        }
        //2.属性拷贝
        CouponDetailVO couponDetailVO = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        //2.1.根据couponId查询
        LambdaQueryWrapper<CouponScope> lqw = new LambdaQueryWrapper<>();
        lqw.eq(CouponScope::getCouponId,id);
        List<CouponScope> list = scopeService.list(lqw);
        if (CollUtils.isEmpty(list)) {
            return couponDetailVO;
        }
        List<CouponScopeVO> couponScopeVOList = new ArrayList<>(list.size());
        for (CouponScope couponScope : list) {
            //
            CouponScopeVO couponScopeVO = BeanUtils.copyBean(couponScope, CouponScopeVO.class);
            couponScopeVOList.add(couponScopeVO);
        }
        couponDetailVO.setScopes(couponScopeVOList);
        return couponDetailVO;

    }

    @Override
    public void pauseIssue(Long id) {
        //1.查询旧优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw  new BadRequestException(COUPON_NOT_EXISTS);
        }
        //2.当前券状态必须是未开始或者进行中
        CouponStatus status = coupon.getStatus();
        if(!UN_ISSUE.equalsValue(status.getValue())&&!ISSUING.equalsValue(status.getValue())){
            throw  new BadRequestException("状态错误");
        }
        //3.正常状态，更新状态
        boolean success = lambdaUpdate()
                .set(Coupon::getStatus, PAUSE.getDesc())
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, UN_ISSUE.getValue(), ISSUING.getValue())
                .update();
        if (!success) {
            //可能为重复更新
            return;
        }
        //4.删除优惠券缓存
        redisTemplate.delete(COUPON_CODE_SERIAL_KEY+id);
    }

    @Override
    public void issueCouponByPage(int page, int size) {
        //1.分页查询“未开始的优惠价”
        Page<Coupon> p = lambdaQuery().eq(Coupon::getStatus, UN_ISSUE.getDesc())
                .page(new Page<>(page, size));
        //2.判断是否有需要处理的数据
        List<Coupon> records = p.getRecords();
        if (CollUtils.isEmpty(records)) {
            return;
        }
        //3.找到需要处理的数据。优惠券过期了的
        LocalDateTime now = LocalDateTime.now();
        List<Coupon> list = records.stream()
                .filter(r -> now.isAfter(r.getIssueBeginTime()))
                .collect(Collectors.toList());
        if (list.size()<1) {
            //没有到期的券，就不用做处理
            return;
        }
        log.info("需要处理的过期券数量:{}",list.size());
        List<Long> ids = list.stream().map(Coupon::getId).collect(Collectors.toList());
        lambdaUpdate()
                .set(Coupon::getStatus,ISSUING.getValue())
                .in(Coupon::getId,ids)
                .update();
        // 5.找到手动领取的优惠券，建立Redis缓存
        list.stream()
                .filter(c->ObtainType.PUBLIC.equalsValue(c.getObtainWay().getValue()))
                .forEach(this::cacheCouponInfo);
    }

    private void cacheCouponInfo(Coupon coupon) {
        Map<String , String > map = new HashMap<>();
        map.put("issueBeginTime",String.valueOf(DateUtils.toEpochMilli(coupon.getIssueBeginTime())));
        map.put("issueEndTime",String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
        map.put("totalNum",String.valueOf(coupon.getTotalNum()));
        map.put("userLimit",String.valueOf(coupon.getUserLimit()));
        redisTemplate.opsForHash().putAll(COUPON_CACHE_KEY_PREFIX+ coupon.getId(),map);
    }

    @Override
    public void deleteById(Long id) {
        log.info("根据id删除优惠券");
        LambdaQueryWrapper<Coupon> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Coupon::getId,id).eq(Coupon::getStatus,DRAFT);
        boolean success = remove(lqw);

        if (!success) {
            throw  new BadRequestException(COUPON_NOT_EXISTS_OR_USEING);
        }
    }

    private void checkObtainWay(CouponFormDTO couponDTO) {
        ObtainType obtainWay = couponDTO.getObtainWay();
        if (obtainWay!=null&& ObtainType.PUBLIC.equalsValue(obtainWay.getValue())) {
            if(couponDTO.getTotalNum()<1||couponDTO.getTotalNum()>=5000){
                throw new BadRequestException("优惠券数量不正确!");
            }
        }

    }
}
